Must-know-things for Java Developers.
数组
构建
数组的声明有以下几种方式,可以选择声明的同时进行定义。1
2
3
4double [] array = new double[3];
double array [] = new double[3];
double [] array = {1.0, 2.0, 3.0};
double array [] = {1.0, 2.0, 3.0};特点
- 数组是一个对象,上例中的array是引用,但原生类型数组中的每一个元素均为原生类型。
长度
- 数组一旦声明,长度是固定的,无法改变。
- 长度不能在声明中定义,如
int arr[5];
或int[5] arr;
均无法通过编译。 - 数组对象有一个成员变量
public final int length;
,可以读取数组长度。
包装类
包括Boolean、Byte、Character、Short、Integer、Long、Float、Double,对应基本类型bool、byte、char、short、int、long、float、double。
- 用途
对原生数据类型封装和解封装,使其对象化,从而可以对其按照对象的方式进行操作。例如在使用泛型时,只能传入对象,因此若传入泛型的是基本类型,则需要对其进行包装。 装箱与拆箱(Boxing & Unboxing)
1
2
3
4
5
6
7int digit = 10; //原生类型,非对象
Integer integer = new Integer(digit); //装箱,将原生类型用封装类来对象化
Integer integer2 = Integer.valueOf(digit); //另一种装箱
int newInt = integer.intValue();//拆箱: obj.xxxValue();
Integer integer = 100; //自动装箱, 编译时调用了Integer.valueOf(100);
int i = integer; //自动拆箱, 编译时调用了integer.intValue();解析字符串
1
2String str="123";
Integer.parseInt(str);上下限分别为
x.MAX_VALUE
和x.MIN_VALUE
,如Integer.MAX_VALUE
及Integer.MIN_VALUE
。
String
字符串(String)是一个在Java程序里应用非常广泛的类。
构造
1
2
3
4
5
6String a = String(); //构造一个空的String对象,内容为空,但不是null
String b = String("string"); //带参构造
char[] chars = {'a','b','c'};
String c = String(char); //由字符数组转换而来
byte[] bytes = {1, 1, 1, 1};
String(byte[]); //由字节数组转换而来public final class String
由于String类被final修饰,因此其拒绝任何继承。这是为了保证String对象的immutable特性,对象的字符串内容,一旦初始化完毕就不能再更改。当我们String s = "abc";
之后,再s = "def";
时,实际上开辟了两个内存空间。对比
两个字符串有两种对比方式:equals方法和”==”。
equals方法重写了Object类的同名方法,仅仅比较两个字符串值的内容。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public boolean equals(Object anObject) {
if(this == anObject) {
return true;
}
if(anObject instanceof String) {
String anotherString = (String) anObject;
int n = value.length;
if(n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while(n-- != 0){
if(v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
}“==”比较的是引用,既要对比两个引用所指对象在堆栈中的地址,又要比较对象的值(相当于调用equals方法),因此”==”的对比更加严格。
实例1:1
2
3
4String str1 = new String("abc");
String str2 = new String("abc"); //实际上在堆中开辟了两个内存空间
str1 == str2; // false,“==”运算为比较地址,两个String对象的引用所指向的对象的地址不同
str1.equals(str2); // true,equals为比较内容,两个对象的引用所指向的字符串的内容相等实例2:
1
2
3String str3 = "abc";
String str4 = "abc"; //已经在常量池中存在,不再重新分配空间
str3 == str4; // true,指向常量池的相同对象地址常量池
String的API在对原字符串进行直接改变时,并不会真正改变原字符串所指向的值,而是生成新的字符串(如果没有),并将引用指向这个新生成的字符串。1
2
3
4
5
6
7String str = "123";
//1.堆内存中开辟一个空间,存放"123"
//2.空间地址赋给栈内存中的对象引用str
str += "456";
//str --解除引用-x->"123"
//str ---> "123456","123"及"456"依然存在于常量池中
//String类没有定义减法“-”运算,故不能用String求字符串差集。
常用方法
getBytes
将String对象的内容转换成byte数组。1
2
3//编码转换
byte[] getBytes(); //使用平台默认字符集
byte[] getBytes(String charSet); //指定字符集equals
用于对比两个String对象的内容。1
2
3String str1 = new String("abc");
String str2 = new String("abc");
str1.equals(str2); //trueequalsIgnoreCase
忽略大小写的equals。1
2
3String str1 = new String("abc");
String str2 = new String("AbC");
str1.equalsIgnoreCase(str2); //truecompareTo
对比两个字符串,返回两个字符串的内容按字典顺序对比的int结果。1
2
3String str1 = "abc";
String str2 = "abd";
str1.compareTo(str2); //小于0,"abc" '<' "abd",若相等则为0startsWith
检查String对象是否以某个子字符串开头。1
2String str1 = "Hello World";
str1.startsWith("Hello"); //true,字符串以Hello开头endsWith
检查String对象是否以某个子字符串结尾。1
2String str1 = "Hello World";
str1.startsWith("World"); //true,字符串以World结尾indexOf
返回某个子字符串在该String对象中第一次出现的索引位置。1
2String str = "Hello World";
str.indexOf("o"); //4,取索引值,第一个匹配的位置lastIndexOf
返回某个子字符串在该String对象中最后一次出现的索引位置。1
2String str = "Hello World";
str.indexOf("o"); //7,取索引值,最后一个匹配的位置charAt
获取位于指定索引位置的字符。1
2
3String str = "Hello World";
str.charAt(1); //e,取指定索引位置的字符
//String不能用str[n]取其中的字符,只能用charAt取。substring
取子字符串,包含beginIndex位置的字符,不包含endIndex的字符。1
2
3
4String str1 = "Hello World";
String str2 = str1.substring(6); //World,取子字符串,从索引index至字符串尾
String str1 = "Hello World";
String str2 = str1.substring(0,5); //Hello,取子字符串,从索引beginindex至endindexconcat
连接两个String对象,生成一个新的字符串。1
2
3String str1 = "Hello ";
String str2 = "World";
String str3 = str1.concat(str2);//Hello World,连接字符串,并返回新字符串replace
将String对象中的某个字符全部替换成另一个字符,生成一个新字符串。1
2String str1 = "Hezzo Worzd";
String str2 = str1.replace('z','l'); //Hello World,替换字符串中部分字符,并返回新字符串replaceAll
将String对象中所有符合指定正则表达式的子字符串替换成另一个字符串,生成一个新字符串。1
2String a = "a111a";
String b = a.replaceAll("\\d", "a"); //b为"aaaaa"replaceFirst
与replaceAll类似,但只替换第一个符合正则表达式的子字符串。1
2String a = "a111a";
String b = a.replaceFirst("\\d", "a"); //b为"aa11a"trim
去掉String对象的前后空格,生成一个新字符串。1
2String str1 = " Hello World ";
String str2 = str1.trim(); //Hello World,去掉字符串前后空格toUpperCase
String对象的所有小写字符转大写,生成一个新字符串。1
2String str1 = "hello world";
String str2 = str1.toUpperCase(); //HELLO WORLD,转大写,并返回toLowerCase
与toUpperCase类似,所有大写字符转小写。1
2String str1 = "HeLlO wOrLd";
String str2 = str1.toLowerCase(); //hello world,转小写,并返回split
以指定字符串为界分割字符串,分割后的每个子字符串组成一个字符串数组的元素。1
2
3String str = "John|male|28";
String[] temp = split("|");
//temp[]={"John","male","28"}
StringBuffer
相较于String,StringBuffer可直接对原字符串进行更改,而不生成新的字符串,便于更新。
构造
1
2
3StringBuffer sb = new StringBuffer(); //默认缓存16字节
StringBuffer sb1 = new StringBuffer(100); //指定起始内存大小
StringBuffer sb2 = new StringBuffer("abc"); //指定起始字符串
常用方法
append
1
2sb.append("123"); //123,追加,改变原StringBuffer对象
sb.append("456"); //123456replace
1
sb.replace(3,5,"xx"); //1 2 3 x x 6,修改beginindex至endindex处(不含)的字符串,改变原StringBuffer对象
insert
1
sb.insert(5,"yy"); //1 2 3 x x y y 6,在index后面的位置插入字符串,改变原StringBuffer对象
delete
1
sb.delete(sb.length()-5,sb.length()); //123,删除自beginindex至endindex处(不含)的字符串,改变原StringBuffer对象
setCharAt
1
sb.setCharAt(2,'4'); //124,改变指定索引的字符,改变原StringBuffer对象
StringBuilder
StringBuilder是StringBuffer的一个非线程安全的简易替换,它们的常用方法名称及用法基本相同。一般来说,StringBuilder用于单线程时,对经常变化的字符串操作性能要优于StringBuffer,但是多线程环境下StringBuilder是不安全的。
String vs. StringBuffer vs. StringBuilder
- StringBuffer和StringBuilder在对经常变化的字符串处理时的性能均优于String,在对字符串常量拼接时的性能差于String。
String类被final修饰,指向字符串常量,每次更改指向新常量。拼接时,如果都是常量,则在编译阶段生成为一个新的常量(如果没有生成过),速度很快;如果包含其他String对象,速度较慢。
1
2String s = "hello" + " " + "world" + "!"; // 编译器优化生成"hello world!"
String s1 = "hello", s2 = "world", s3 = s1 + s2; // 较慢StringBuffer线程安全,StringBuilder非线程安全,两者每次改变的都是对象本身。
String对象的字符串拼接有时被解释成StringBuilder对象的拼接,e.g.
1
2
3String s1 = "hello ";
String s2 = "world!";
String s3 = s1 + s2; //解释成String s3 = new StringBuilder(s1).append(s2).toString();性能方面
- 普遍来说,StringBuilder > StringBuffer > String。
特殊情况下,如对于拼接常量字符串,String的性能强于其余两者,e.g.
1
2
3String str = "He" + "llo" + " " + "Wo" + "rl" + "d";
StringBuffer sBuffer = new StringBuffer("He").append("llo").append(" ").append("Wo").append("rl").append("d");
StringBuilder sBuilder = new StringBuilder("He").append("llo").append(" ").append("Wo").append("rl").append("d");
常用类
System
用静态变量保存全局重要信息,所有成员都是静态的。
成员变量
- err
一个PrintStream对象,用于输出系统错误信息,默认目的地为STDERR:System.err.println();
。 - out
一个PrintStream对象,用于输出系统普通信息,默认目的地为STDOUT:System.out.println();
。 - in
一个InputStream对象,用于获取系统输入,默认源为STDIN设备:System.in.println();
。
常用方法
currentTimeMills
获取时间,返回此时距1970年1月1日0点整的毫秒数。1
long currentTimeMillis(); //start from 1970-1-1 00:00:00 in mill seconds
exit
停止JVM。1
void System.exit(0);//0-正常退出
setErr / setOut / setIn
手动指定err/out/in成员变量,这样当执行System.out.println();
等代码时会往指定的输入源/输出目的地进行输入/输出。环境变量
对系统环境变量的操作。1
2
3
4
5
6static Map<String, String> getenv();
static String getenv(String name);
static Properties getProperties();
static void setProperties(Properties props);
static String getProperty(String key);
static void setProperty(String key, String value);
Runtime
JVM运行时相关信息,对象是单例,只能通过Runtime r = Runtime.getRuntime();
获取对象。
exec
新起进程,调用指定的外部命令。1
Process exec("xxx.exe");
freeMemory / totalMemory / maxMemory / availableProcessors
查看JVM的系统资源信息。
Date
简单的日期时间类,用于获取日期信息。在Java SE 8中,有一个替代这个类的更方便的java.time包。
构造
1
2Date date = new Date();//date当前时间:Weekday Month Day hh:mm:ss CST year
Date date = new Date(long mills);//自1970年1月1日08:00分开始往后指定毫秒的日期时间
Calendar
由于Date类不便于国际化,因此JDK提供了Calendar这个复杂的日期时间类,它是一个抽象类,其唯一实现子类为GregorianCalendar。
构造
1
2Calendar c = Calendar.getInstance(); //父类构造,返回的是当前时间的GregorianCalendar对象
GregorianCalendar g = new GregorianCalendar();读取
1
2
3c.get(Calendar.YEAR);
c.get(Calendar.MONTH);
c.get(Calendar.DAY_OF_MONTH);静态变量还包括:WEEK_OF_MONTH、WEEK_OF_YEAR、DATE(同DAY_OF_MONTH)、DAY_OF_WEEK、DAY_OF_YEAR、HOUR(12小时制)、HOUR_OF_DAY(24小时制)、MINUTE、SECOND等等。
add
1
c.add(Calendar.YEAR,1); //明年此时
set
1
c.set(Calendar.YEAR, Calendar.YEAR+1); //明年此时
DateFormat
将Date类对象格式规整,并能将Date与String互转的抽象类,常用子类为SimpleDateFormat。
构造
1
2
3SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd E HH:mm:ss:SSS");
//年-月-日 星期几 时:分:秒:毫秒
//HH-24小时制,hh-12小时制,a-am/pmDate转字符串
1
String dateStr = sdf.format(date);
字符串转Date
1
sdf.parse(dateStr);
Math
几何、三角相关运算方法,均为静态。
- 取绝对值
1 | int abs(int value); |
- 三角函数
1 | double acos(double a); |
- 取对数
1 | double log(double value); |
- 求幂
1 | double pow(double a, double b); |
- 比大小
1 | double max(double a, double b); |
Random
产生基于时间的伪随机数。
1 | int nextInt(); //随机整数 |
内部类
内部类是指在一个类的内部再定义一个类,在编译成功后,会生成Outer.class及Outer$Inner.class两个文件。
成员内部类
作为外部类的成员定义在外部类的内部,依赖于外部类,需要外部类对象才能生成内部类对象。
1
2
3
4
5
6
7
8
9class Outer {
class Inner{}
}
...
public static void main(String[] args)
{
Outer o = new Outer();
Outer.Inner i = o.new Inner();
}对于成员变量及方法,内部类和外部类的权限相同。另外,非静态的成员内部类不可以声明静态方法和变量。
1
2
3
4
5
6
7
8
9
10
11
12class Outer {
private int i;
private static int s;
private void f(){}
class Inner{
public Inner() {
i = 1;
s = 2;
f();
}
}
}在内部类引用外部类对象时,使用[外部类名].this来获取。
1
2
3
4
5
6
7
8
9public class Outer {
private int i;
class Inner {
public int i;
public Inner() {
i = Outer.this.i;
}
}
}
局部内部类
定义在方法里或代码块里,作用域限制在定义范围内。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public static void main(String[] args)
{
class InnerInMethod{
protected boolean flag = true;
protected boolean getFlag(){
return flag;
}
}
InnerInMethod inMethod = new InnerInMethod();
if(inMethod.getFlag()){
class InnerInBlock extends InnerInMethod{
protected boolean getFlag() {
return !super.getFlag();
}
}
InnerInBlock inBlock = new InnerInBlock();
System.out.println(inBlock.getFlag());
}
}
静态内部类
- 使用static关键字修饰类时,只能修饰内部类.
- 非静态内部类不能用static关键字修饰成员.
- 静态内部类不能访问外部类的非静态成员,只能访问外部类的静态变量或方法。
对于静态内部类,内部类对象的生成不依赖于外部类对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class Outer {
private int i;
private static int s;
private void f(){}
private static void s(){}
private static class Inner{
public Inner() {
//i = 1; 不能引用成员变量
s = 2;
//f(); 不能调用成员方法
s();
}
}
}
...
Outer.Inner inner = new Outer.Inner();
匿名内部类
- 匿名内部类或其父类/接口需预先定义。
匿名内部类不能加任何访问修饰符。
1
2
3
4
5
6
7
8
9
10
11
12
13((Button) findViewById(R.id.start)).setOnClickListener(new Button.OnClickListener() {
//匿名的OnClickListener接口实现类
public void onClick(View v) {
new Thread() {
//匿名的Thread子类
public void run() {
// TODO Auto-generated method stub
}
}.start();
}
});
枚举
Coding过程中,为了统一常量规范,有时需要定义一些固定数目和数值的集合,比如天气、月份、季节,我们也许会这样定义:
1 | public class GlobalConstant { |
这样定义显然很费劲,为了更好地封装常量,我们可以使用枚举。枚举可以看作一个特殊的类,所有枚举均继承自java.lang.Enum。
- 必须将枚举成员置于枚举定义的顶部。
- 构造方法只能由默认或private修饰。
- 可以用括号为每个枚举成员赋值,指定若干个属性 e.g. RED(“red”, 1),需要:
- 添加对应的构造方法[e.g. Color(String name, int index)]。 - 添加对应类型的成员变量[e.g. String name; int code;](可选,但如果不将括号赋值的值保存在成员变量的话,则无法获取枚举成员的属性)。
下面这段代码,总结了枚举的大部分定义方式及用法。
1 | /** |
关键字
transient
transient关键字只能用于修饰变量。当对象被序列化/反序列化时,被transient关键字修饰的变量不会被持久化/恢复。
比如类中有一个InputStream对象,对其序列化/反序列是不合适的,因为序列化和反序列化的位置可能不同,从而导致恢复时对象不可用。
volatile
volatile也是变量修饰符,只能用来修饰变量。volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。